package org.xbl.xchain.sdk.crypto.algo;

import com.google.common.base.Splitter;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.HDUtils;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.xbl.xchain.sdk.SysConfig;
import org.xbl.xchain.sdk.crypto.encode.ConvertBits;

import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.util.List;

public abstract class Algorithm {
    private final int TRUNCATED_SIZE = 20;

    static {
        try {
            if (null == Security.getProvider("BC")) {
                Security.addProvider(new BouncyCastleProvider());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public BigInteger getBigIntegerFromMnemonic(String mnemonic) {
        List<String> words = Splitter.on(" ").splitToList(mnemonic);
        byte[] seed = Mnemonic.INSTANCE.toSeed(words, "");
        DeterministicKey key = HDKeyDerivation.createMasterPrivateKey(seed);
        List<ChildNumber> childNumbers = HDUtils.parsePath(SysConfig.HD_PATH);
        for (ChildNumber cn : childNumbers) {
            key = HDKeyDerivation.deriveChildKey(key, cn);
        }
        return key.getPrivKey();
    }

    private AlgorithmType type;

    public Algorithm(AlgorithmType type) {
        this.type = type;
    }

    public AlgorithmType getType() {
        return this.type;
    }

    abstract public PrivateKey genPrivateKeyFromMnemonic(String mnemonic) throws Exception;

    abstract public PrivateKey genPrivateKeyFromPriKeyString(String privateKey) throws Exception;

    abstract public PublicKey genPublicKey(PrivateKey privateKey) throws Exception;

    abstract public AsymmetricKeyParameter genPrivateKeyParameterFromMnemonic(String mnemonic) throws Exception;

    abstract public AsymmetricKeyParameter genPubKeyParameter(AsymmetricKeyParameter privateKeyParameter);


    abstract public byte[] sign(PrivateKey privateKey, byte[] msg) throws Exception;

    abstract public byte[] sign(AsymmetricKeyParameter privateKey, byte[] msg) throws Exception;

    byte[] sign(PrivateKey privateKey, byte[] msg, String bcAlgorithm) throws Exception {
        Signature signature;
        signature = Signature.getInstance(bcAlgorithm, "BC");
        signature.initSign(privateKey);
        signature.update(msg);
        return decodeSignature(signature.sign());
    }

    abstract public String genAddressFromPublicKey(String mainPrefix, PublicKey publicKey) throws Exception;

    abstract public boolean verifySignature(PublicKey publicKey, byte[] msg, byte[] signature) throws Exception;

    abstract public boolean verifySignature(AsymmetricKeyParameter publicKey, byte[] msg, byte[] signature) throws Exception;

    boolean verifySignature(PublicKey publicKey, byte[] msg, byte[] signature, String bcAlgorithm) throws Exception {
        Signature verifier;
        verifier = Signature.getInstance(bcAlgorithm, "BC");
        signature = encodeSignature(signature);
        verifier.initVerify(publicKey);
        verifier.update(msg, 0, msg.length);
        return verifier.verify(signature);
    }

    abstract byte[] decodeSignature(byte[] signature) throws Exception;

    abstract byte[] encodeSignature(byte[] derSig) throws Exception;

    public static byte[] hashDigest(byte[] input, int offset, int length, String algo) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance(algo);
        digest.update(input, offset, length);
        return digest.digest();
    }

    public static byte[] encode(int witnessVersion, byte[] witnessProgram) throws AddressFormatException {
        byte[] convertedProgram = ConvertBits.convertBits(witnessProgram, 0, witnessProgram.length, 8, 5, true);
        return convertedProgram;
    }

    abstract public byte[] parsePubKey(PublicKey publicKey) throws Exception;

}
